package raft

import (
	"6.824/labgob"
	"bytes"
)

type InstallSnapshotRequest struct {
	Term             int
	LeaderID         int
	LastIncludeIndex int
	LastIncludeTerm  int
	Data             []byte
}

type InstallSnapshotResponse struct {
	Term int
}

func (rf *Raft) InstallSnapshot(args *InstallSnapshotRequest, reply *InstallSnapshotResponse) {
	rf.mu.Lock()

	//Reply immediately if term < currentTerm
	if args.Term < rf.term {
		reply.Term = rf.term
		rf.mu.Unlock()
		return
	} else {
		rf.changeState <- 0
		if args.Term > rf.term {
			rf.term = args.Term
			rf.votedFor = -1
		}
		reply.Term = rf.term
	}

	//Save snapshot file, discard any existing or partial snapshot
	// with a smaller index
	if args.LastIncludeIndex > rf.lastIncludedIndex {
		rf.lastIncludedIndex = args.LastIncludeIndex
		rf.lastIncludedTerm = args.LastIncludeTerm
		lastLogIndex, _ := rf.getLastLogIndexAndTerm()
		if args.LastIncludeIndex >= lastLogIndex {
			rf.logs = make([]Log, 0)
		} else {
			// If existing log entry has same index and term as snapshot’s
			//last included entry, retain log entries following it and reply
			for idx, log := range rf.logs {
				if log.LogIndex == args.LastIncludeIndex {
					rf.logs = rf.logs[idx+1:]
					break
				}
			}
		}
		//Reset state machine using snapshot contents (and load
		//snapshot’s cluster configuration)
		w := new(bytes.Buffer)
		e := labgob.NewEncoder(w)
		e.Encode(rf.term)
		e.Encode(rf.votedFor)
		e.Encode(rf.logs)
		e.Encode(rf.lastIncludedIndex)
		e.Encode(rf.lastIncludedTerm)
		data := w.Bytes()
		rf.persister.SaveStateAndSnapshot(data, args.Data)
	}
	appMsg := ApplyMsg{
		SnapshotValid: true,
		Snapshot:      args.Data,
		SnapshotIndex: rf.lastIncludedIndex,
		SnapshotTerm:  rf.lastIncludedTerm,
	}
	rf.mu.Unlock()
	rf.applyCh <- appMsg
}
